package com.lifeonwalden.fincalc.finance.service;

import com.lifeonwalden.copula.ArchimedeanCopula;
import com.lifeonwalden.copula.constant.ArchimedeanCopulaFamily;
import com.lifeonwalden.copula.constant.ArchimedeanCopulaMethod;
import com.lifeonwalden.copula.constant.CopulaConstants;
import org.apache.commons.math3.stat.correlation.Covariance;
import org.apache.commons.math3.stat.correlation.PearsonsCorrelation;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

public interface RelationshipCalculator {

    /**
     * 计算协方差
     *
     * @param aDatas a变化数据
     * @param bDatas b变化数据
     * @return
     */
    static BigDecimal covariance(double[] aDatas, double[] bDatas) {
        if (aDatas.length != bDatas.length) {
            throw new RuntimeException("非法数据");
        }
        return BigDecimal.valueOf(new Covariance().covariance(aDatas, bDatas, false));
    }

    /**
     * 计算相关性
     *
     * @param aDatas a变化数据
     * @param bDatas b变化数据
     * @return
     */
    static BigDecimal correlation(double[] aDatas, double[] bDatas) {
        if (aDatas.length != bDatas.length) {
            throw new RuntimeException("非法数据");
        }
        return BigDecimal.valueOf(new PearsonsCorrelation().correlation(aDatas, bDatas));
    }

    /**
     * 相关性计算
     *
     * @param aDataList
     * @param bDataList
     * @param valueFetcher
     * @param <T>
     * @return
     */
    static <T> BigDecimal correl(List<T> aDataList, List<T> bDataList, Function<T, BigDecimal> valueFetcher) {
        return correl(aDataList, valueFetcher, bDataList, valueFetcher);
    }

    /**
     * 相关性计算
     *
     * @param aDataList
     * @param aValueFetcher
     * @param bDataList
     * @param bValueFetcher
     * @param <A>
     * @param <B>
     * @return
     */
    static <A, B> BigDecimal correl(List<A> aDataList, Function<A, BigDecimal> aValueFetcher, List<B> bDataList, Function<B, BigDecimal> bValueFetcher) {
        if (aDataList.size() != bDataList.size()) {
            throw new RuntimeException("数据项不匹配");
        }

        List<BigDecimal> aList = new ArrayList<>(),
                bList = new ArrayList<>();

        BigDecimal aSum = BigDecimal.ZERO, bSum = BigDecimal.ZERO;

        for (int i = 0; i < bDataList.size(); i++) {
            A aItem = aDataList.get(i);
            B bItem = bDataList.get(i);

            if (null != aItem && null != bItem) {
                BigDecimal aValue = aValueFetcher.apply(aItem), bValue = bValueFetcher.apply(bItem);

                if (null != aValue && null != bValue) {
                    aSum = aSum.add(aValue);
                    aList.add(aValue);

                    bSum = bSum.add(bValue);
                    bList.add(bValue);
                }
            }
        }

        BigDecimal aAvg = aSum.divide(BigDecimal.valueOf(aList.size()), 12, RoundingMode.DOWN), bAvg = bSum.divide(BigDecimal.valueOf(aList.size()), 12, RoundingMode.DOWN);

        BigDecimal aDiff = BigDecimal.ZERO, bDiff = BigDecimal.ZERO;
        BigDecimal diffProductSum = BigDecimal.ZERO;
        BigDecimal aDiffSqrSum = BigDecimal.ZERO, bDiffSqrSum = BigDecimal.ZERO;

        for (int i = 0; i < aList.size(); i++) {
            aDiff = aList.get(i).subtract(aAvg);
            bDiff = bList.get(i).subtract(bAvg);

            diffProductSum = diffProductSum.add(aDiff.multiply(bDiff));
            aDiffSqrSum = aDiffSqrSum.add(aDiff.pow(2));
            bDiffSqrSum = bDiffSqrSum.add(bDiff.pow(2));
        }

        BigDecimal result = BigDecimal.valueOf(Math.pow(diffProductSum.pow(2).divide(aDiffSqrSum.multiply(bDiffSqrSum), 12, RoundingMode.DOWN).doubleValue(), 0.5)).setScale(4, RoundingMode.DOWN);
        if (diffProductSum.compareTo(BigDecimal.ZERO) < 0) {
            return result.multiply(BigDecimal.valueOf(-1));
        } else {
            return result;
        }
    }

    /**
     * 计算尾相关性
     *
     * @param aDatas a变化数据
     * @param bDatas b变化数据
     * @return
     */
    static BigDecimal correlationLower(List<BigDecimal> aDatas, List<BigDecimal> bDatas) {
        if (aDatas.size() != bDatas.size()) {
            throw new RuntimeException("非法数据");
        }

        List<BigDecimal>[] sampleData = new ArrayList[2];
        sampleData[0] = aDatas;
        sampleData[1] = bDatas;
        ArchimedeanCopula archimedeanCopula = new ArchimedeanCopula(ArchimedeanCopulaFamily.CLAYTON);
        archimedeanCopula.fit(sampleData, ArchimedeanCopulaMethod.CMLE, Optional.empty());

        return BigDecimal.valueOf(Math.pow(2, CopulaConstants.MINUS_ONE.divide(archimedeanCopula.getParameter(), 16, RoundingMode.DOWN).doubleValue()));
    }
}
